﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Visuals;
using Silvermoon.Controls.Classes;
using System.Drawing;
using System.Diagnostics;
using Silvermoon.Animations;
using Silvermoon.Controls.Interfaces;

namespace Silvermoon.Controls.Base
{
    public class ListViewBase : EventControl, IMouseEventBehavior, IScrollEventBehavior, IInputEventBehavior, ITransitionBehavior
    {
        #region constants

        /// <summary>
        /// Secifies the interval in milliseconds after which the scrollbar will fade of when the touchpad is released.
        /// </summary>
        public const int HideScrollbarDelayValue = 3500;

        #endregion
        #region Fields

        public const int BorderSize = 0;

        /// <summary>
        /// Specifies the alpha value for headers.
        /// </summary>
        public const int HeaderAlpha = 220;

        /// <summary>
        /// Specifies the alpha value for the header at the top.
        /// </summary>
        public const int TopHeaderAlpha = 220;

        private int mouseTop;
        private bool initializing;
        private VScrollBar vScrollBar;
        private bool showScrollBar;
        protected int scrollBarAlpha = 255;
        private ControlCollection subControls;
        private int top;
        private ItemBase selectedItem;
        private Animation moveAnimation;
        private Margin containerMargin;
        private Rectangle marginRect;
        private Margin selectMargin;
        private int hideScrollbarTick;

        #endregion
        #region ctor/dtor

        public ListViewBase()
            : base()
        {
            subControls = new ControlCollection(this);
            Items = new VirtualItemCollection(this);
            ItemHeight = Control.ScaleY(40);
            vScrollBar = new VScrollBar() { Alpha = 220, Visible = false };
            vScrollBar.ValueChanged += new EventHandler(OnScrollbarValueChanged);
            containerMargin = new Margin(BorderSize);
        }

        void OnScrollbarValueChanged(object sender, EventArgs e)
        {
            TopOffset = vScrollBar.Value;
        }

        public override void Dispose()
        {
            StopScrolling();
            ShowScrollbar = false;
            if (moveAnimation != null) moveAnimation.Abort();
            base.Dispose();
        }

        #endregion
        #region Properties


        protected VScrollBar VScrollBar { get { return vScrollBar; } }

        /// <summary>
        /// Gets the margin for the items.
        /// </summary>
        public Margin ItemsMargin { get { return containerMargin; } set { if (containerMargin != value) { containerMargin = value; OnSizeChanged(); } } }

        /// <summary>
        /// Gets or sets the margin in which an item can be selected.
        /// </summary>
        public Margin SelectMargin { get { return selectMargin; } set { if (selectMargin != value) { selectMargin = value; OnSizeChanged(); } } }


        /// <summary>
        /// Gets whether the ListView is between an BeginInit...EndInit state.
        /// </summary>
        public bool IsInitializing
        {
            get { return initializing; }
        }


        /// <summary>
        /// Gets the rectangle that describes the area where to place the items.
        /// </summary>
        protected Rectangle MarginRect
        {
            get
            {
                if (marginRect.IsEmpty)
                {
                    Margin m = this.containerMargin;
                    Rectangle r = ClientBounds;
                    r.Offset(m.LeftZero, m.TopZero);
                    r.Width -= m.LeftZero + m.RightZero;
                    r.Height -= m.TopZero + m.BottomZero;
                    marginRect = r;
                }
                return marginRect;
            }
        }

        public int TotalHeight
        {
            get { return Items.TotalHeight; }
        }

        public int ItemCount
        {
            get { return Items.ItemCount; }
            set
            {
                if (Items.ItemCount != value)
                {
                    Items.ItemCount = value;
                    Invalidate(InvalidationFlag.LayoutAppearance | InvalidationFlag.Custom);
                }
            }
        }

        public ItemBase SelectedItem
        {
            get { return selectedItem; }
            set
            {
                if (selectedItem != value)
                {
                    if (value is ISelectable)
                    {
                        OnSelectedItemChanged(selectedItem, value);
                    }
                }
            }
        }

        /// <summary>
        /// Gets or sets the top offset of for the items.
        /// </summary>
        public int TopOffset
        {
            get { return top; }
            set
            {
                if (!CanScroll) value = 0;
                if (top != value)
                {
                    Invalidate(InvalidationFlag.LayoutAppearance);
                    int old = value;
                    top = value;
                    OnTopOffsetChanged(old, value);
                    vScrollBar.Value = value;
                }
            }
        }

        /// <summary>
        /// Gets whether scrolling is enabled.
        /// </summary>
        protected virtual bool CanScroll { get { return true; } }

        /// <summary>
        /// Gets or sets whether to show the scrollbar.
        /// </summary>
        public virtual bool ShowScrollbar
        {
            get { return showScrollBar; }
            set
            {
                if (value) value = CanShowScrollbar();
                if (value)
                {
                    vScrollBar.Alpha = 220;
                    hideScrollbarTick = Environment.TickCount;
                }
                if (showScrollBar != value)
                {
                    showScrollBar = value;
                    OnShowScrollbarChanged();
                }
            }
        }

        /// <summary>
        /// Gets the collection of items.
        /// </summary>
        public VirtualItemCollection Items { get; private set; }



        /// <summary>
        /// Gets or sets the height of a virtual Item. This property is required to determine the maximum value of TopOffset that the ListView can have in virtual mode.
        /// </summary>
        public virtual int ItemHeight
        {
            get { return Items.ItemHeight; }
            set { Items.ItemHeight = value; }
        }


        #endregion
        #region Methods

        /// <summary>
        /// Determines whether an item is selected.
        /// </summary>
        /// <param name="item">The item to check.</param>
        /// <returns>True, if the item is selected, otherwise false</returns>
        /// <remarks>
        /// This method is used by <see cref="T:ItemBase"/> to determine if it should appear selected rather than just checking ListViewBase.SelectedItem==this, since with this
        /// method, you can override IsItemSelected to tell an item never to be rendered as selected, which is done with <see cref="T:MagnifyingView"/>.
        /// </remarks>
        public virtual bool IsItemSelected(ItemBase item)
        {
            return item == SelectedItem;
        }

        protected override void OnSizeChanged()
        {
            marginRect = Rectangle.Empty;
            base.OnSizeChanged();
            if (SelectedItem != null)
            {
                PreLayout();
                EnsureVisible(SelectedItem);
            }
            if (!CheckFlag(InvalidationFlag.Uninitialized))
            {
                EnsureValidOffset(true);
            }
        }

        /// <summary>
        /// Determines whether the scrollbar can be shown, which means that the virtual size is larger than the display size:
        /// </summary>
        /// <returns>True, if the scrollbar can be shown, otherwise false.</returns>
        private bool CanShowScrollbar()
        {
            return Height < TotalHeight;
        }

        internal protected virtual ItemBase OnAttachItem(int index)
        {
            if (AttachItem != null)
            {
                ItemEventArgs e = new ItemEventArgs(index);
                AttachItem(this, e);
                ItemBase item = e.Item;
                return item;
            }
            return null;
        }

        internal protected virtual void OnDetachItem(int index, ItemBase item)
        {
            item.OnDetach(index);
            if (DetachItem != null)
            {
                ItemEventArgs e = new ItemEventArgs(index) { Item = item };
                DetachItem(this, e);
            }
        }


        protected virtual void OnTopOffsetChanged(int oldOffset, int newOffset)
        {
            // ShowScrollbar = true;
        }

        private List<ItemBase> headers = new List<ItemBase>();
        protected List<ItemBase> Headers { get { return headers; } }


        /// <summary>
        /// Creates and/or adjusts all the controls that are visible at the current state.
        /// </summary>
        protected virtual void OnLayoutChildControls()
        {
            subControls.Clear();
            int topItemIndex = GetItemAt(TopOffset);
            Margin m = ItemsMargin;
            Rectangle marginRect = new Rectangle(m.LeftZero, m.TopZero, Width - m.LeftZero - m.RightZero, Height - m.TopZero - m.BottomZero);
            if (topItemIndex >= 0)
            {
                headers.Clear();
                int topOffset = Items.GetOffset(topItemIndex);
                for (int i = topItemIndex; i < ItemCount; i++)
                {
                    ItemBase item = Items[i];
                    item.Bounds = new System.Drawing.Rectangle(
                        marginRect.Left,
                        topOffset - TopOffset + marginRect.Top,
                        marginRect.Width, item.Height);

                    topOffset += item.ActualHeight;
                    if ((item.Y >= marginRect.Bottom)) break;
                    if (item is ItemHeader)
                    {
                        headers.Add(item);
                        item.Alpha = HeaderAlpha;
                        if (item.Y < 0)
                        {
                            item.Location = new Point(item.X, 0);
                        }
                    }
                    else
                    {
                        subControls.Add(item);
                    }
                }


                //ListViewHeader header = IsVirtual ? null : ListViewUtil.HeaderBelowIndex(topItemIndex, Items, ItemCount);
                int headerIndex = Items.GetHeaderIndexAboveIndex(topItemIndex);
                ItemHeader header = null;
                if (headerIndex >= 0)
                {
                    header = Items[headerIndex] as ItemHeader;
                    subControls.Remove(header);
                    header.Alpha = TopHeaderAlpha;
                    header.Bounds = new Rectangle(marginRect.Top, marginRect.Left, marginRect.Width, header.Height);
                    subControls.Add(header);
                }

                foreach (var h in headers)
                {
                    if (h != header) subControls.Add(h);
                }
                headers.Clear();
            }

            if (vScrollBar != null && vScrollBar.Visible) subControls.Add(vScrollBar);

        }

        public int GetItemAt(int TopOffset)
        {
            return Items.GetItemIndexFromTop(top);
        }



        protected virtual void OnSelectedItemChanged(ItemBase oldItem, ItemBase newItem)
        {
            selectedItem = newItem;
            if (oldItem != null) oldItem.OnSelectedChanged();
            if (newItem != null) newItem.OnSelectedChanged();
            if (SelectedItemChanged != null) SelectedItemChanged(this, new ItemEventArgs(-1) { Item = newItem });
            Invalidate(InvalidationFlag.Appearance | InvalidationFlag.Custom);
        }

        public virtual void OnItemClick(ItemBase item)
        {
            if (ItemClicked != null)
            {
                ItemClicked(this, new ItemEventArgs(-1) { Item = item });
            }
        }

        /// <summary>
        /// Bounces to the default position, if out of range.
        /// </summary>
        /// <param name="animate">if set to true, animates to the valid TopOffset, otherwise sets the TopOffset directly.</param>
        public virtual void EnsureValidOffset(bool animate)
        {
            Rectangle mr = MarginRect;
            if (TotalHeight > mr.Height)
            {
                int top = 0;
                int height = mr.Bottom;
                if (TopOffset < top) ScrollTo(top);
                else if ((TopOffset + height + top) > TotalHeight)
                {
                    int topOffset = TotalHeight - height - top;
                    if (animate) ScrollTo(topOffset); else TopOffset = topOffset;
                }
            }
            else
            {
                if (TopOffset != 0) ScrollTo(0);
            }
        }

        public void ScrollTo(int top)
        {
            StopScrolling();
            moveAnimation = new Animation(200, TopOffset, top, 0);
            moveAnimation.Progress += new EventHandler<AnimEventArgs>(OnScrolling);
            moveAnimation.InvokeAsync();
        }

        public void ScrollToSync(int top)
        {
            StopScrolling();
            moveAnimation = new Animation(Animation.DefaultDuration, TopOffset, top, 0);
            moveAnimation.Progress += new EventHandler<AnimEventArgs>(OnScrolling);
            moveAnimation.Invoke();
        }

        void OnScrolling(object sender, AnimEventArgs e)
        {
            TopOffset = e.Value;
        }

        protected virtual void StopScrolling()
        {
            if (moveAnimation != null)
            {
                moveAnimation.Abort();
            }
        }

        public int IndexOfItem(ItemBase item)
        {
            return Items.IndexOf(item);
        }

        public ItemBase GetItem(int index)
        {
            return Items[index];
            //return ListViewUtil.GetNextItemBelowIndex(index, Items);
        }

        public void SelectNextItem()
        {
            int startIndex = SelectedItem == null ? 0 : SelectedItem.ItemIndex + 1;
            for (int i = startIndex; i < ItemCount; i++)
            {
                ItemBase item = GetItem(i);
                if (item is ISelectable)
                {
                    SelectedItem = item;
                    EnsureVisible(item);
                    item.Focus();
                    return;
                }
            }

        }

        public void SelectPrevItem()
        {
            int startIndex = SelectedItem == null ? ItemCount - 1 : SelectedItem.ItemIndex - 1;
            if (startIndex >= 0)
            {
                for (int i = startIndex; i >= 0; i--)
                {
                    ItemBase item = GetItem(i);
                    if (item is ISelectable)
                    {
                        SelectedItem = item;
                        EnsureVisible(item);
                        item.Focus();
                        return;
                    }
                }
            }
        }

        public virtual void ClickSelectedItem()
        {
            ItemBase item = SelectedItem as ItemBase;
            if (item is ISelectable) item.OnClick();
        }

        /// <summary>
        /// Ensures that the specified item is visible, and changes TopOffset to make it visible, if not.
        /// </summary>
        /// <param name="item">The item to check.</param>
        public virtual void EnsureVisible(ItemBase item)
        {
            int index = Items.IndexOf(item);
            int top = Items.GetOffset(index);
            int bottom = top + item.Height;
            int topOffset = 0 * BorderSize;
            int yMax = MarginRect.Height - SelectMargin.BottomZero;
            int yMin = SelectMargin.TopZero;
            if (ItemCount > 0 && (GetItem(0) is ItemHeader))
            {
                topOffset += GetItem(0).Height;
            }

            if ((bottom - TopOffset) > (yMax))
            {
                TopOffset = -(yMax - bottom);
            }
            else if ((top - TopOffset) < (topOffset + yMin))
            {
                TopOffset = -(topOffset + yMin - top);
            }
        }

        public virtual void FocusItem()
        {
            var item = SelectedItem;
            if (item == null) item = ItemCount > 0 ? GetItem(0) : null;
            EnsureVisible(item);
            if (item != null && (item is ISelectable)) item.Focus();
        }

        public virtual void SelectItem(ItemBase item)
        {
            SelectedItem = item;
        }

        protected virtual void OnShowScrollbarChanged()
        {
            if (showScrollBar)
            {
                VScrollBar.Alpha = 220;
                VScrollBar.Visible = true;
                Screen.Tick -= HideScrollbarDelay;
                subControls.Add(VScrollBar);
            }
            else
            {
                hideScrollbarTick = Environment.TickCount;
                Screen.Tick += HideScrollbarDelay;
            }
            Invalidate(InvalidationFlag.LayoutAppearance);
        }

        void HideScrollbarDelay(object sender, Silvermoon.Core.TimeEventArgs e)
        {
            int dt = e.Tick - hideScrollbarTick;
            if (dt > HideScrollbarDelayValue)
            {
                dt -= HideScrollbarDelayValue;
                int alpha = 255 - dt / 2;
                if (alpha < 0) alpha = 0;
                if (alpha > 220) alpha = 220;
                VScrollBar.Alpha = alpha;
                if (alpha <= 0)
                {
                    Screen.Tick -= HideScrollbarDelay;
                    subControls.Remove(VScrollBar);
                    VScrollBar.Visible = false;
                }
            }
        }

        #endregion
        #region events

        public event EventHandler<ItemEventArgs> AttachItem;
        public event EventHandler<ItemEventArgs> DetachItem;

        public event EventHandler<ItemEventArgs> SelectedItemChanged;
        public event EventHandler<ItemEventArgs> ItemClicked;

        #endregion
        #region ICompositeControl Members

        //IList<Control> ICompositeControl.Controls
        //{
        //    get { return childControls; }
        //}

        protected internal override IList<Control> SubControls
        {
            get { return subControls; }
        }

        #endregion
        #region IMouseEvents

        public override void OnMouseDown(MouseDownEventArgs e)
        {
            // don't care if e.Handled is set to true, always perform the following:
            StopScrolling();
            e.Handled = true;
            base.OnMouseDown(e);
        }

        public override void OnMouseUp(MouseUpEventArgs e)
        {
            StopScrolling();
            base.OnMouseUp(e);
            ShowScrollbar = false;
            if (e.Reason == MouseUpReason.ButtonUp) EnsureValidOffset(true);
        }


        protected override void PreLayout()
        {
            if ((InvalidationMask & (InvalidationFlag.Margin | InvalidationFlag.Size)) != 0)
            {
                marginRect = Rectangle.Empty;
            }
            base.PreLayout();
        }

        protected override void OnLayout()
        {
            if ((InvalidationMask & InvalidationFlag.LayoutAppearance | InvalidationFlag.Size) != 0)
            {
                OnLayoutChildControls();
                AdjustScrollbar();
            }
            //base.InitializeControl();
        }

        private void AdjustScrollbar()
        {
            vScrollBar.Bounds = Control.ScaledRect(Width - vScrollBar.Width, BorderSize, vScrollBar.Width, Height - BorderSize * 2);
            vScrollBar.Maximum = TotalHeight;
            vScrollBar.LargeChange = Height;
        }

        internal void ItemHeightChanged(int index)
        {
        }

        public virtual void ItemRemoved(int index, ItemBase removed)
        {
            if (removed == SelectedItem)
            {
                int newIndex = index >= ItemCount ? index = index - 1 : index;
                SelectedItem = newIndex >= 0 ? ListViewUtil.GetNextItemBelowIndex(newIndex, Items) : null;
            }
            if (!initializing)
            {
                if (!CanShowScrollbar()) ShowScrollbar = false;
            }
        }

        public virtual void ItemAdded(int index)
        {
        }

        protected virtual void OnItemsChanged()
        {
        }

        /// <summary>
        /// Begins to initialize the Items so no live update and individual calculation for one item is done.
        /// </summary>
        public virtual void BeginInit()
        {
            Items.Initializing = true;
            initializing = true;
        }

        /// <summary>
        /// Ends initialization of Items and performs necassary calculations and updates.
        /// </summary>
        public virtual void EndInit()
        {
            Items.Initializing = false;
            initializing = false;
            if (!CanShowScrollbar()) ShowScrollbar = false;
            EnsureValidOffset(false);
            Invalidate(InvalidationFlag.LayoutAppearance);
        }

        #endregion
        #region IScrollDataBehavior Members

        /// <summary>
        /// Same as OnMouseMove but occurs even if the mouse is actually not pressed due to simulated accelleration.
        /// </summary>
        /// <param name="e"></param>
        public virtual void OnScroll(ScrollEventArgs e)
        {
            if (!e.Handled && notifyScoll)
            {
                ShowScrollbar = true;
                e.Handled = true;
                int dy = e.Position.Y - e.StartPosition.Y;
                TopOffset = mouseTop - dy;
                int top = TopOffset;
                if (!e.IsMouseDown)
                {
                    if (top < 0 || (top + ClientHeight) > TotalHeight) e.Cancel = true;
                }
            }
        }

        bool notifyScoll = false;

        /// <summary>
        /// Occurs befor sending intervals of <see cref="ScrollEventArgs"/>.
        /// </summary>
        /// <param name="e"></param>
        public virtual void OnBeginScrollData(BeginScrollEventArgs e)
        {
            if (!e.Handled)
            {
                // only accept ScrollEventArgs to receive if there is a vertical movent and not a horizontal,
                // but to not cancel the message to let other parent controls decide whether to accept horizontal scrolling:
                if (e.Ay > e.Ax)
                {
                    notifyScoll = true;
                    ShowScrollbar = true;
                    mouseTop = TopOffset;
                    e.Handled = true;
                    if (!CanScroll) e.Cancel = true;
                }
                else notifyScoll = false;
            }
        }

        /// <summary>
        /// Occurs after sending the last <see cref="ScrollEventArgs"/>.
        /// </summary>
        /// <param name="e"></param>
        public virtual void OnEndScrollData(EndScrollEventArgs e)
        {
            notifyScoll = false;
            ShowScrollbar = false;
            e.Handled = true;
            EnsureValidOffset(true);
        }


        /// <summary>
        /// Gets whether the control currently accepts receiving of ScrollEventArgs.
        /// </summary>
        /// <value></value>
        public bool AcceptScrollData
        {
            get { return true; }
        }

        #endregion
        #region IInputEventBehavior Members

        public virtual void OnKeyDown(System.Windows.Forms.KeyEventArgs e)
        {
            ShowScrollbar = true;
            if (!e.Handled)
            {
                switch (e.KeyCode)
                {
                    case System.Windows.Forms.Keys.Down:
                        e.Handled = true;
                        SelectNextItem();
                        break;

                    case System.Windows.Forms.Keys.Up:
                        e.Handled = true;
                        SelectPrevItem();
                        break;
                }
            }
        }

        public virtual void OnKeyUp(System.Windows.Forms.KeyEventArgs e)
        {
            ShowScrollbar = false;
        }

        public virtual void OnKeyPress(System.Windows.Forms.KeyPressEventArgs e)
        {
            if (!e.Handled)
            {
                switch (e.KeyChar)
                {
                    case (char)13:
                        e.Handled = true;
                        ClickSelectedItem();
                        break;
                }
            }
        }

        public override void OnGotFocus()
        {
            base.OnGotFocus();
        }

        public virtual bool CanFocus { get { return true; } }

        #endregion
        #region ITransitionBehavior Members

        void ITransitionBehavior.StopAnimations()
        {
            ShowScrollbar = false;
            StopScrolling();
        }

        #endregion

    }
}
